package top.someapp.applib.kits;

import java.util.Arrays;
import top.someapp.applib.AppException;
import top.someapp.applib.SpecialTexts;

/**
 * byte数组操作帮助类
 *
 * @author zwz
 * Created on 2019-06-13
 */
@SuppressWarnings({"WeakerAccess", "unused"})
public final class Bytes {

    static final char[] hexChars = "0123456789abcdef".toCharArray();
    static final char EMPTY_CHAR = SpecialTexts.EMPTY_CHAR;

    private final byte[] bytes;

    private Bytes(byte[] bytes) {
        this.bytes = bytes;
    }

    // statics
    public static Bytes from(byte[] bytes) {
        return new Bytes(bytes);
    }

    public static Bytes from(int[] ints) {
        return new Bytes(byteArrayFromHex(ints));
    }

    public static Bytes fromHex(String hex) {
        return new Bytes(byteArrayFromHex(hex));
    }

    public static Bytes fromHex(String hex, char delimiter) {
        if (delimiter == EMPTY_CHAR) {
            return fromHex(hex);
        }
        return new Bytes(byteArrayFromHex(Strings.stripChar(hex, delimiter)));
    }

    public static byte[] byteArrayFromHex(int[] ints) {
        byte[] bytes = new byte[ints.length];
        for (int i = 0, len = ints.length; i < len; i++) {
            bytes[i] = (byte) ints[i];
        }
        return bytes;
    }

    static int intValue(char ch) {
        int val;
        if (ch >= '0' && ch <= '9') {
            val = ch - '0';
        } else if (ch >= 'a' && ch <= 'f') {
            val = 10 + ch - 'a';
        } else if (ch >= 'A' && ch <= 'F') {
            val = 10 + ch - 'A';
        } else {
            throw new AppException("Invalid hex char: " + ch);
        }
        return val;
    }
    // ~String operations

    public static byte[] byteArrayFromHex(String hex) {
        hex = "0" + hex.trim(); // 最高位先补个0
        int i = hex.length() % 2; // 长度是偶数还是奇数
        byte[] bytes = new byte[hex.length() / 2];
        for (int len = hex.length(); i <= len - 2; i += 2) {
            int val = intValue(hex.charAt(i)) * 16;
            val += intValue(hex.charAt(i + 1));
            bytes[i / 2] = (byte) val;
        }
        return bytes;
    }

    /**
     * 将整数val转换成16进制字符串
     *
     * @param val 待转换的整数
     * @param evenLength 是否保持偶数位长度，true：转换后的长度为奇数时最高位补0；false：不作任何处理
     * @return 16进制字符串
     */
    public static String hexStringOfInt(int val, boolean evenLength) {
        if (val < 0) {
            throw new AppException("Convert a negative number to hex String may has problems!");
        }
        if (val == 0) {
            return evenLength ? "00" : "0";
        }
        StringBuilder hex = new StringBuilder();
        for (; val > 0; val >>= 4) {
            int low4Bits = val & 0xf;
            hex.insert(0, hexChars[low4Bits]);
        }
        if (evenLength && hex.length() % 2 != 0) {
            hex.insert(0, "0");
        }
        return hex.toString();
    }

    public static String byteHexString(byte b) {
        if (b == 0) {
            return "00";
        }
        if (b == -1) {
            return "ff";
        }
        return new String(new char[]{hexChars[(b >> 4) & 0x0f], hexChars[b & 0xf]});
    }

    public static byte[] shortToBytes(short val) {
        return Arrays.copyOfRange(intToBytes(val), 2, 4);
    }
    // ~number operations

    public static byte[] intToBytes(int val) {
        byte[] bytes = {0, 0, 0, 0};
        for (int i = bytes.length - 1; val != 0 && i >= 0; i--) {
            bytes[i] = (byte) (val & 0xff);
            val >>= 8;
        }
        return bytes;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public byte[] getBytesFixedLength(int length) {
        if (length == bytes.length) {
            return bytes;
        }
        return Arrays.copyOf(bytes, length);
    }

    // String operations
    public String hexString() {
        return hexString(EMPTY_CHAR);
    }

    public String hexString(char delimiter) {
        StringBuilder hex = new StringBuilder();

        if (delimiter == EMPTY_CHAR) {
            for (byte b : bytes) {
                hex.append(byteHexString(b));
            }
        } else {
            boolean first = true;
            for (byte b : bytes) {
                if (first) {
                    first = false;
                } else {
                    hex.append(delimiter);
                }
                hex.append(byteHexString(b));
            }
        }

        return hex.toString();
    }

    public String hexString(char delimiter, int count) {
        StringBuilder hex = new StringBuilder();

        if (delimiter == EMPTY_CHAR) {
            for (byte b : bytes) {
                hex.append(byteHexString(b));
            }
        } else {
            boolean first = true;
            for (byte b : bytes) {
                if (first) {
                    first = false;
                } else {
                    hex.append(delimiter);
                }
                hex.append(byteHexString(b));
                count--;
                if (count <= 0) {
                    break;
                }
            }
        }

        return hex.toString();
    }

    public String hexString(int index) {
        if (index < 0 || index >= bytes.length) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        return byteHexString(bytes[index]);
    }

    public String asciiString() {
        StringBuilder ascii = new StringBuilder();

        for (byte b : bytes) {
            if (b < 0) {
                throw new AppException(String.format("Invalid ascii code: %02x", b));
            }
            ascii.append((char) b);
        }

        return ascii.toString();
    }

    // number operations
    public int intValueByLength(int start, int length) {
        int val = 0;
        for (int i = 1; i <= length; i++) {
            int b = 0xff & bytes[start + i - 1];
            val <<= 8;
            val |= b;
        }
        return val;
    }

    public Number numberByLength(int start, int length) {
        return Integer.valueOf(intValueByLength(start, length));
    }

    public int intValue(int index, boolean signed) {
        if (index < 0 || index >= bytes.length) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        return signed ? bytes[index] : bytes[index] & 0xff;
    }

    public int uIntValue(int index) {
        return intValue(index, false);
    }
    // ~statics
}
